package com.ibm.util;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

import org.apache.log4j.Logger;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.gson.Gson;
import com.ibm.cps.sql.expression.ComparisonExpression;
import com.ibm.util.exception.CPSException;

/**
 * @author Yaoliang Chen
 * @version May 20, 2014 json operation utility
 */

public class JSONUtility {
	private static Logger logger = Logger.getLogger(JSONUtility.class);
	private static ObjectMapper mapper = new ObjectMapper();
	// static private org.json.simple.parser.JSONParser parser = new
	// org.json.simple.parser.JSONParser();
	static private Gson gson = new Gson();

	static public JsonNode newJSONNode(Object v) {
		return mapper.valueToTree(v);
	}

	static public ObjectNode newObjectNode() {
		return mapper.createObjectNode();
	}

	static public ArrayNode newArrayNode() {
		return mapper.createArrayNode();
	}

	static public JsonNode fromString(String json) throws CPSException {
		if (json == null) {
			return newObjectNode();
		}
		try {
			return mapper.readTree(json);
		} catch (IOException e) {
			throw new CPSException(ErrorCode.PARSE_ERROR_CODE,
					"Parse error for " + json + ": " + e.getMessage());
		}
	}

	static public ObjectNode fromObjectString(String json) throws CPSException {
		if (json == null) {
			return newObjectNode();
		}

		try {
			JsonNode object = mapper.readTree(json);
			return (ObjectNode) object;
		} catch (Exception e) {
			throw new CPSException(ErrorCode.PARSE_ERROR_CODE,
					"Parse error for json object " + json + ": "
							+ e.getMessage());
		}
	}

	static public JsonNode fromStringWithNoException(String json) {
		if (json == null) {
			return newObjectNode();
		}

		try {
			return mapper.readTree(json);
		} catch (IOException e) {
			logger.error(e.getMessage());
		}
		return null;

	}
	
	static public String toJsonStringByJacksonAPI(Object obj) throws CPSException{
		try {
			return mapper.writeValueAsString(obj);
		} catch (JsonProcessingException e) {
			throw new CPSException(ErrorCode.INTERNAL_ERROR_CODE, "Failed to convert object " + obj + " to JSON string", e);
		}
	}

	static public String toJsonString(Object obj) {
		return gson.toJson(obj);
	}
	
	static public <T> T fromJsonToObjectWithClassName(String json, String className) throws CPSException{
		try {
			Class<?> type = Class.forName(className);
			return (T)mapper.readValue(json, type);
		} catch (Exception e){
			e.printStackTrace();
			throw new CPSException(ErrorCode.INTERNAL_ERROR_CODE, "Failed to deserialize json: " + json + " to type: " + className, e);
		}
	}

	static public Object fromJsonString(String json, Type t) {
		return gson.fromJson(json, t);
	}

	static public ArrayNode objsToArray(ObjectNode[] objs) {
		ArrayNode msgArr = newArrayNode();
		for (ObjectNode rec : objs) {
			msgArr.add(rec);
		}
		return msgArr;
	}

	static public int GetInteger(String tag, ObjectNode postJson,
			int defaultValue) {
		/* process the int type param */
		JsonNode valueNode = postJson.get(tag);
		if (valueNode == null) {
			return defaultValue;
		}

		if (valueNode.isTextual()) {
			try {
				return Integer.parseInt(valueNode.asText());
			} catch (NumberFormatException e) {
				return defaultValue;
			}
		} else if (valueNode.canConvertToInt()) {
			return valueNode.asInt();
		}
		return defaultValue;
	}

	static public String getString(String tag, String json) throws CPSException {
		JsonNode object = JSONUtility.fromString(json);
		if (object.isArray()) {
			throw new CPSException(ErrorCode.INVALID_INPUT_ERROR, json
					+ " isn't a json element.");
		}
		if (object.get(tag) == null) {
			return null;
		} else {
			return object.get(tag).asText();
		}
	}

	static public String GetString(String tag, ObjectNode postJson,
			String defaultValue) {
		/* process the int type param */
		JsonNode valueNode = postJson.get(tag);
		if (valueNode == null) {
			return defaultValue;
		}
		return valueNode.asText();
	}

	static public String GetString(String tag, ObjectNode postJson) {
		/* process the int type param */
		JsonNode valueNode = postJson.get(tag);
		if (valueNode == null) {
			return null;
		}
		return valueNode.asText();
	}

	static public long GetLong(String tag, ObjectNode postJson,
			long defaultValue) {
		/* process the int type param */
		JsonNode valueNode = postJson.get(tag);
		if (valueNode == null) {
			return defaultValue;
		}

		if (valueNode.isTextual()) {
			/* we need to return default value if failed */
			try {
				return Long.parseLong(valueNode.asText());
			} catch (NumberFormatException e) {
				return defaultValue;
			}
		} else if (valueNode.canConvertToLong()) {
			return valueNode.asLong();
		}
		return defaultValue;
	}

	/**
	 * 
	 * @param tag
	 * @param jsonBody
	 * @param providerId
	 * @param action
	 * @param collector
	 * @param essential
	 * @return < 0 do not found the field; null fatal error
	 * @throws CPSException
	 */
	public static long GetTimestamp(DateParser dateParser, String tag,
			ObjectNode jsonBody, boolean essential) throws CPSException {
		/* process the style tag */
		long timestamp = -1L;

		if (!jsonBody.has(tag)) {
			if (essential) {
				throw new CPSException(ErrorCode.PARSE_ERROR_CODE, "need tag '"
						+ tag + "'");
			}
			return -1L;
		}

		try {
			timestamp = dateParser.getTimestamp(jsonBody.get(tag));
		} catch (Exception e) {
			logger.error("need tag '" + tag + "'");
			throw new CPSException(ErrorCode.PARSE_ERROR_CODE, "need tag '"
					+ tag + "'");
		}

		return timestamp;
	}

	public static Object getObjectFromNode(JsonNode v) {
		if (v.isBoolean()) {
			return v.asBoolean();
		} else if (v.isFloatingPointNumber()) {
			return v.asDouble();
		} else if (v.canConvertToLong()) {
			return v.asLong();
		} else {
			return v.asText();
		}

	}

	public static boolean GetBoolean(String tag, ObjectNode postJson) {
		return GetBoolean(tag, postJson, false);
	}

	public static boolean GetBoolean(String tag, ObjectNode postJson,
			boolean defaultValue) {
		/* process the int type param */
		JsonNode valueNode = postJson.get(tag);
		if (valueNode == null) {
			return defaultValue;
		}
		return valueNode.asBoolean();
	}

	public static ObjectNode newObjectNode(String k, String v) {
		return newObjectNode().put(k, v);
	}

	public static ArrayList<String> getArrayList(String arrayString)
			throws CPSException {

		JsonNode node = fromString(arrayString);
		if (!(node instanceof ArrayNode)) {
			throw new CPSException(ErrorCode.INTERNAL_ERROR_CODE, arrayString
					+ " is not a json array.");
		}
		ArrayNode arrayNode = (ArrayNode) node;
		ArrayList<String> array = new ArrayList<String>();
		for (int i = 0; i < arrayNode.size(); i++) {
			array.add(arrayNode.get(i).asText());
		}
		return array;
	}

	public static ArrayNode newArrayNode(Collection<String> arrays)
			throws CPSException {
		if (arrays == null) {
			return null;
		}
		ArrayNode array = newArrayNode();
		for (String object : arrays) {
			array.add(JSONUtility.fromString(object));
		}
		return array;
	}

	public static boolean checkNotNull(ObjectNode node, String field)
			throws CPSException {
		if (node.get(field) == null) {
			throw new CPSException(ErrorCode.NULL_FIELD_ERROR,
					"Please provide the value of " + field + ".");
		}
		return true;
	}

	public static String assignRandomValue(ObjectNode node, String fieldName) {
		if (!node.has(fieldName)) {
			String id = RandomUtil.getRandomId();
			node.put(fieldName, id);
			return id;
		} else {
			return node.get(fieldName).asText();
		}
	}

	public static ArrayNode getArrayFromString(String input)
			throws CPSException {
		JsonNode object = fromString(input);
		return getArrayFromNode(object);
	}

	public static ArrayNode getArrayFromNode(JsonNode object) {
		if (object.isArray()) {
			return (ArrayNode) object;
		} else {
			ArrayNode array = newArrayNode();
			array.add(object);
			return array;
		}
	}

	public static HashMap<String, String> getKeyValue(String input)
			throws CPSException {
		HashMap<String, String> key2Value = new HashMap<String, String>();
		ObjectNode objectNode = JSONUtility.fromObjectString(input);
		Iterator<String> ite = objectNode.fieldNames();
		String fieldName;
		while (ite.hasNext()) {
			fieldName = ite.next();
			if (objectNode.get(fieldName) instanceof ArrayNode){
				key2Value.put(fieldName, ((ArrayNode)objectNode.get(fieldName)).toString());
			} else if(objectNode.get(fieldName) instanceof ObjectNode) {
				key2Value.put(fieldName, objectNode.get(fieldName).toString());
			} else {
				key2Value.put(fieldName, objectNode.get(fieldName).asText());
			}
		}
		if (key2Value.size() == 0) {
			return null;
		}
		return key2Value;
	}

	public static ArrayNode convertString2Array(String value) {
		ArrayNode node = newArrayNode();
		node.add(value);
		return node;
	}

	public static ObjectNode convertField2Array(ObjectNode element, String field)
			throws CPSException {
		if (isStringField(element, field)) {
			element.set(field, convertString2Array(element.get(field).asText()));
		}
		return element;
	}

	public static boolean isStringField(ObjectNode node, String fieldName) {
		return node.has(fieldName) && node.get(fieldName).isTextual();
	}

	public static boolean isJsonObjectField(ObjectNode node, String fieldName) {
		return node.has(fieldName) && node.get(fieldName).isObject();
	}

	public static boolean isArrayField(ObjectNode node, String fieldName) {
		boolean isArray = node.has(fieldName) && node.get(fieldName).isArray();
		if (isArray) {
			return isArray;
		}
		if (node.has(fieldName)) {
			String value = node.get(fieldName).asText();
			try {
				JsonNode objectnode = fromString(value);
				node.set(fieldName, objectnode);
				return objectnode.isArray();
			} catch (CPSException e) {
				return false;
			}
		} else {
			return false;
		}
	}

	public static boolean isIntField(ObjectNode node, String fieldName) {
		if (node.has(fieldName)) {
			String value = node.get(fieldName).asText();
			return canConvertToInt(value);
		} else {
			return false;
		}
	}

	private static boolean canConvertToInt(String value) {
		try {
			Integer.parseInt(value);
			return true;
		} catch (Exception e) {
			return false;
		}
	}

	public static boolean isBooleanField(ObjectNode node, String fieldName) {
		return node.has(fieldName) && node.get(fieldName).isBoolean();
	}

	public static void merge(ObjectNode destination, ObjectNode source) {
		destination.setAll(source);
	}

	public static boolean isEquals(ObjectNode node1, ObjectNode node2) {
		// if (node1 == null && node2 == null) {
		// return true;
		// } else if (node1 == null || node2 == null) {
		// return false;
		// }
		// Iterator<String> fields = node1.fieldNames();
		// while(fields.hasNext()){
		// String field = fields.next();
		// if(node2.has(field)){
		// if(node1.get(field).isValueNode() && node2.get(field).isValueNode())
		// {
		// // node1.get(field).eq
		// }
		// }else{
		// return false;
		// }
		// }
		// return false;
		return node1.equals(node2);
	}

}
